/*
 * ChessServerSocket.java
 *
 * Created on 16.9.2007, 21:57:37
 *
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package cz.possoft.server.threads;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;

import cz.possoft.generic.utils.GenericUtils;
import cz.possoft.server.main.ChessServer;

/**
 * Vlakno ChessServerSocket slouzi ke komunikaci s prave jednim klientem.
 * Seznam vsech hracu (vlaken ChessServerSocket) na strane serveru je v poli players.
 * 
 * @author mara
 */
public class ChessServerSocket extends Thread {

    private Socket socket;
    //private BufferedReader reader;
    //private PrintWriter writer;
    private ObjectInputStream oReader;
    private ObjectOutputStream oWriter;
    
    private String socketInput; // pomocny string na retezce.
    private int inic = 0; // 0 socket jeste nebyl spojen. Pri odeslani CONNECT na server a uspesnem

    // spojeni se inic zmeni na 1
    public String getNick() {
        return nick;
    }

    public void setNick(String n) {
        nick = n;
    }

    public ChessServerSocket getSouper() {
        return souper;
    }

    public int getInic() {
        return inic;
    }

    private ChessServerSocket souper = null; // Souper tohoto hrace.
    private String nick; // nick tohoto hrace
    //private static volatile ArrayList<Thread> players = new ArrayList<Thread>(); // pole s vlakny klientu (vcetne tech co maji name = null)
    private static volatile Thread[] players = new Thread[ChessServer.MAXPLAYERS];
    static int players_count = 0; // pocet pripojenych klientu (vcetne tech co maji name = null)
    
    // index vlakna, kde se vyskytuje prvni hrac (ChessServerSocket)
    public static final int FIRST_PLAYER_INDEX = 2;

    // konstruktor
    public ChessServerSocket(Socket s) {
        this.socket = s;
        System.out.println("Server prijal spojeni: " + s);
        try {
//            oReader = new ObjectInputStream(new BufferedInputStream(socket.getInputStream()));
//            oWriter = new ObjectOutputStream(new BufferedOutputStream(socket.getOutputStream()));
            oReader = new ObjectInputStream(socket.getInputStream());
            oWriter = new ObjectOutputStream(socket.getOutputStream());
//            reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//            writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true);
        } catch (IOException ioe) {
            System.out.println("IOException");
            ioe.printStackTrace();
        }
        start();
    }

    // vraci stav tohoto hrace. Stav muze byt a) FREE b) OCCUPIED c) ME
    public String getStater() {
        if (souper == null) {
            return "FREE";
        }
        if (souper != null) {
            return "OCCUPIED";
        }
        return null;
    }

    private String getPripojeni(boolean showThisFlag) {
        // Seznam nicku pripojenych klientu oddeleny carkami.
// presny format: Josef FREE, Frantisek FREE, Dalimil OCCUPIED
        // pokud je showThisFlag nastaven na false, tak se nevypise tento klient (volano pri CLOSE: )
        int j;

        String s = "CONNECTED: ";
        j = FIRST_PLAYER_INDEX;
        boolean flag = true;
        while (flag) {
            Object playerPom = (ChessServerSocket) getPlayers()[j];
            if ((playerPom != null) && (playerPom instanceof ChessServerSocket)) {
                ChessServerSocket player = (ChessServerSocket) playerPom;
                if ((player.getInic() == 1) && ((showThisFlag == true) || (player != this))) {
                    s = s + player.getNick() + " " + player.getStater() + ", ";
                }
            } else {
                flag = false;
            }
            j++;
        }
        return s;
    }

    // synchronizovana metoda. Obnovi pole se seznamem pripojenych hracu a jejich pocet
    private void refresh() {
        
        getThreadGroup().list();
        //Thread[] playersPom = new Thread[ChessServer.MAXPLAYERS];
        //List<Thread> playersPomm = Arrays.asList(playersPom);
        //players = (ArrayList)Arrays.asList(playersPom);
        players_count = getThreadGroup().enumerate(players, true);
        
        // VYNULOVANI POSLEDNIHO HRACE. jinak tam zustavaji viset prazdni hraci.
        players[players_count] = null;
        //int size = playersPomm.size();
        //players = (ArrayList)Arrays.asList(playersPom);
        //System.out.println("refresh pom: " + players.size());
    }

    // synchronizovana metoda na vraceni pole hracu
    public static Thread[] getPlayers() {
        return players;
    }
    
    // synchronizovana metoda na vraceni pole hracu
    public static void setPlayers(Thread[] players) {
        ChessServerSocket.players = players;
    }

    // synchronizovana metoda
    private int getPlayersCount() {
        return players_count;
        //players_count;
    }

    // vraci true pokud je nick name jiz pritomen.
    private boolean isPresent(String name) {
        refresh();
        //synchronized(this) {
        int j = FIRST_PLAYER_INDEX;
        boolean flag = true;
        while (flag) {
            Object playerPom = (ChessServerSocket) getPlayers()[j];
            if ((playerPom != null) && (playerPom instanceof ChessServerSocket)) {
                ChessServerSocket player = (ChessServerSocket) playerPom;
                if ((player.getNick() != null) && (player.getNick().equals(name))) {
                    return true;
                }
            } else {
                flag = false;
            }
            j++;
        }
        return false;
    }

    // najde soupere podle jmena
    private ChessServerSocket find_souper_by_name(String name) {
        refresh();
        //synchronized(this) {
        int j = FIRST_PLAYER_INDEX;
        boolean flag = true;
        while (flag) {
            Object playerPom = (ChessServerSocket) getPlayers()[j];
            if ((playerPom != null) && (playerPom instanceof ChessServerSocket)) {
                ChessServerSocket player = (ChessServerSocket) playerPom;
                if ((player.getNick() != null) && (player.getNick().equals(name))) {
                    return player;
                }
            } else {
                flag = false;
            }
            j++;
        }
        return null;
    }

    // vsem ostatnim hracum, kteri me vyzvali (krome soupere) posle zruseni vyzvy (duvod OTHER)
//    private void send_other_to_others() 
//   {
//        refresh();
//        //synchronized(this) {
//        int j = FIRST_PLAYER_INDEX;
//        boolean flag = true;
//        while (flag) {
//            Object playerPom = (ChessServerSocket) getPlayers()[j];
//            if ((playerPom != null) && (playerPom instanceof ChessServerSocket)) {
//                ChessServerSocket player = (ChessServerSocket) playerPom;
//                if ((player.getSouper() == this) && (player != souper)) {
//                    player.getWriter().println("OTHER: " + getNick());
//                    player.setSouper(null); // odblokovani soupere
//                }
//            } else {
//                flag = false;
//            }
//            j++;
//        }
//    }

    // vsem hracum zasle CONNECTED: message. (tzn.seznam vsech pripojenych hracu a jejich stav)
    // pokud je showThisFlag nastaven na false, tak se neukazuje tento hrac. (volano pri CLOSE: )
    public void sendConnectedMessageToAll(boolean showThisFlag) {
        String pripojeni = getPripojeni(showThisFlag);
        int j = FIRST_PLAYER_INDEX;
        boolean flag = true;
        while (flag) {
            Object playerPom = (ChessServerSocket) getPlayers()[j];
            if ((playerPom != null) && (playerPom instanceof ChessServerSocket)) {
                ChessServerSocket player = (ChessServerSocket) playerPom;
                try {
                player.sendToClient(pripojeni);
                }
                catch (Exception e) {
                    e.printStackTrace();
                }
            } else {
                flag = false;
            }
            j++;
        }
    }

    // vsem hracum zasle CONNECTED: message. (tzn.seznam vsech pripojenych hracu a jejich stav)
    // hracum kteri maji obsazeneho oponenta pak posle null v pripade, ze tento oponent neexistuje
    // tato metoda je volana z vlakna PlayerChecker
    // public void sendConnectedMessageToAllStatic() {
    //     int j;
    //
    //     String pripojeni = "CONNECTED: " ;
    //     for (j=2 ;  j<getPlayersCount() ; j++) { ChessServerSocket player = (ChessServerSocket)getPlayers()[j];
    //                 pripojeni = pripojeni + player.getNick() + " " + player.getStater() + ", ";
    //     }
    //     for (int i=2 ; i<getPlayersCount() ; i++) { ChessServerSocket player = (ChessServerSocket)getPlayers()[i];
    //                                             player.getWriter().println(pripojeni);
    //
    //                                             // posilame NULL v pripade, ze je oponent NULL
    //                                             if (player.getSouper() == null) {
    //                                                 player.getWriter().println("NULL: ");
    //                                             }
    //                              }
    //
    // }
    // zkontroluje jestli spojeni se souperem nespadlo (nezavrel se socket). Pokud jo tak nastavi soupere na null.
    public void check_souper() {
        if (souper != null) {
            if (souper.getSocket().isClosed()) {
                souper = null;
            }
        }
    }

    public Socket getSocket() {
        return socket;
    }

    // hlavni metoda klienta. Bezi v ni vlakno
    public void run() {
        try {
            sleep(1000);
        } catch (InterruptedException ie) {
            System.err.println("Interr. exception");
        }
        while (true) {

            try {
                Object o = oReader.readObject();
                if (o instanceof String) {
                socketInput = (String)o;                            

            // Zpracovani zpravy od klienta. Zprava je v retezci socketInput            
            if ((socketInput.indexOf("CONNECT: ") == 0) && (inic == 0)) {
                String nick_pom = socketInput.substring(9);
                if (isPresent(nick_pom)) {
                    //oWriter.println("NAME_PRESENT: ");
                    sendToClient("NAME_PRESENT: ");
                } else {
                    nick = nick_pom;
                    refresh();
                    inic = 1;
                    sendConnectedMessageToAll(true);
                    System.out.println("Klient prihlasen s nickem: " + nick);                    
                }
            }
            // Ostatni pozadavky klienta (s vyjimkou CONNECT) bereme v potaz jen pokud je klient pripojen.
            if (inic == 1) {

                if (socketInput.indexOf("CONNECTED: ") == 0) {
                    refresh();
                    sendToClient(getPripojeni(true));
                }

                if (socketInput.indexOf("CLOSE: ") == 0) {
                    System.out.println("Socket " + getNick() + " shozen.");
                    try {
                        socket.close();
                        oWriter.close();
                        oReader.close();
                        sendConnectedMessageToAll(false);
                    } catch (IOException ioe) {
                        System.out.println("IOCLOSE");
                        ioe.printStackTrace();
                    }
                    refresh();
                }

                // VYZVY SOUPERUM A VSE CO S TIM SOUVISI
                // vyzvani soupere (a zablokovani sebe)
                if ((socketInput.indexOf("VYZVAT: ") == 0) && (souper == null)) {
                    String souper_name = GenericUtils.getVyzivatelName(socketInput);
                    String gameKey = GenericUtils.getGameKey(socketInput);
                    ChessServerSocket souper_pom = find_souper_by_name(souper_name);
                    if (souper_pom != null) {
                        if (souper_pom.getStater().equals("FREE")) {
                            // SOUPERE OBSADIME UZ V TOMTO BODE, ABY NEBYLO MOZNO VYZVAT HRACE, KTERY UZ POSLAL VYZVU.
                            // ZAROVEN OBSADIME I U SOUPERE, ABY HO NEVYZYVALI OSTATNI
                            souper = souper_pom;
                            souper_pom.setSouper(this);
                            souper.sendToClient("VYZVAL: " + getNick() + "_" + gameKey);
                            // tady posilame na klienta potvrzeni vyzvy spolu s oponentovym jmenem
                            sendToClient("VYZVA_POTVRZENA: " + souper.getNick());
                            refresh();
                            sendConnectedMessageToAll(true);
                        } else {
                            sendToClient(getPripojeni(true));
                            // sobe other neposilam. Pokud plati podminka v ifu, tak to znamena,
                            // ze me tento souper vyzval driv nez ja jeho.
                            if (!souper_pom.getSouper().getNick().equals(getNick())) {
                            	sendToClient("OTHER: ");                            
                            }
                        }
                    } else {
                        sendToClient(getPripojeni(true));
                        sendToClient("NULL: ");
                    }
                }
                // zruseni vyzvy
                if (socketInput.indexOf("ZRUSIT_VYZVU: ") == 0) {
                    if (souper != null) {
                        souper.sendToClient("ZRUSENA_VYZVA: " + getNick());
                    }
                    souper = null; // odblokovani sebe
                    refresh();
                }
                // neakceptovani souperovy vyzvy
                if (socketInput.indexOf("NOT_ACCEPT: ") == 0) {
                    String souper_name = socketInput.substring(12);
                    ChessServerSocket souper_pom = find_souper_by_name(souper_name);
                    if (souper_pom != null) {
                        souper_pom.sendToClient("NOT_ACCEPT: " + getNick());
                        souper_pom.setSouper(null); // odblokovani soupere                        
                    }
                    setSouper(null); // odblokovani me
                    refresh();
                    sendConnectedMessageToAll(true);
                }
                // akceptovani souperovy vyzvy
                if (socketInput.indexOf("ACCEPT: ") == 0) {
                    String souper_name = socketInput.substring(8);
                    souper = find_souper_by_name(souper_name); // zablokovani sebe. Souper uz je zablokovan.
                    if (souper != null) {
                        refresh();
                        souper.sendToClient("ACCEPT: " + getNick());
                        //writer.println("ACCEPT: " + souper.getNick());
                        sendConnectedMessageToAll(true);
                        //send_other_to_others();
                    } else {
                        sendToClient("NULL: ");
                    }
                }
                
                // doslo potvrzeni od klienta, ze se chce uvolnit od oponenta (napr. po skonceni hry, preruseni spojeni atd.)
                if (socketInput.indexOf("I_AM_FREE: ") == 0) {                    
                    setSouper(null);
                    refresh();
                    sendConnectedMessageToAll(true);
                }                                

                // VLASTNI HRA
                // posleme message souperi. Posilame ji i sobe.
                if ((socketInput.indexOf("SEND_MESSAGE: ") == 0) && (socketInput.length() > 14)) {
                    String message = "GET_MESSAGE: " + getNick() + ": " + socketInput.substring(14);
                    check_souper();
                    if (souper != null) {
                        souper.sendToClient(message);
                        sendToClient(message);
                    } else {
                        sendToClient("NULL: ");
                    }
                }
                // posleme svuj tah souperi
                if (socketInput.indexOf("PLAY: ") == 0) {                    
                    check_souper();
                    if (souper != null) {
                        souper.sendToClient(socketInput);
                    } else {
                        sendToClient("NULL: ");
                    }
                } 
                
                // PESEC_DOSEL dojde pokud muj pesec dosel na posledni pole. Nutno informovat protivnika aby
                // jeste cekal nez si vyberu figurku.
                // VYBRANA_FIGURKA dojde kdyz jsme si vybrali figurku a informujeme o tom
                // protivnika
                // Podobne se preposilaji i navrhy na remizu a prohru nebo jejich akceptovani
                // posilaji se take navrhy na load hry a jejich akceptovani
                if ((socketInput.indexOf("PESEC_DOSEL: ") == 0) || (socketInput.indexOf("VYBRANA_FIGURKA: ") == 0) || (socketInput.indexOf("REMIZA_NAVRH: ") == 0) || (socketInput.indexOf("PROHRA_NAVRH: ") == 0) ||
                     (socketInput.indexOf("ACCEPT_REMIZA: ") == 0) || (socketInput.indexOf("NOT_ACCEPT_REMIZA: ") == 0) || (socketInput.indexOf("LOAD_GAME_VYZVA: ") == 0) || (socketInput.indexOf("LOAD_GAME_ACCEPT: ") == 0) || (socketInput.indexOf("LOAD_GAME_DECLINE: ") == 0)) {                    
                    check_souper();
                    if (souper != null) {
                        souper.sendToClient(socketInput);
                    } else {
                        sendToClient("NULL: ");
                    }
                }                                
            }
                
            }  
                // konec pripadu kdy dosel string
                // dosla game, takze ji preposleme protivnikovi
                else {
                   check_souper();
                    if (souper != null) {
                        souper.sendGameToClient(o);
                    } else {
                        sendToClient("NULL: ");
                    } 
                }
            } catch (Exception ioe) {                                
                ioe.printStackTrace();
                try {
                    socket.close();
                    oReader.close();
                    oWriter.close();
                } catch (IOException ioeo) {
                    System.out.println("Vyjimka pri zavirani socketu v " + getNick());
                }

                destroy();
            }
            // Konec zpracovani zpravy od klienta
        }        
    }

    public void setSouper(ChessServerSocket souper) {
        this.souper = souper;
    }

    public ObjectOutputStream getWriter() {
        return oWriter;
    }
    
    // zasle retezec ze serveru klientovi
    public void sendToClient(String s) {
        try {
        getWriter().writeObject(s);
        getWriter().flush();
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    // posle klientovi Game
    private void sendGameToClient(Object o) {
        try {
        getWriter().writeObject(o);
        getWriter().flush();
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
    
}
